/**
 *    Copyright 2012 Geensoft S.A.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/
package ar.com.greensoft.neo.web.jsf.beans.common;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;

import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.ExtendedDataModel;
import org.ajax4jsf.model.Range;
import org.ajax4jsf.model.SequenceRange;
import org.richfaces.component.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ar.com.greensoft.neo.architecture.Revision;
import ar.com.greensoft.neo.dao.DaoException;
import ar.com.greensoft.neo.dao.GenericWithRevisionDao;
import ar.com.greensoft.neo.search.SearchInput;
import ar.com.greensoft.neo.web.jsf.converters.GenericConverter;
import ar.com.greensoft.neo.web.jsf.util.JSFMenuUtil;

/**
 * @author Jorge E. Villaverde
 * @version 1.0.0
 * @since 04/08/2011	
 */
public abstract class GenericEditRevisionFacadeBean<T, PK extends Serializable>  implements GenericEditFacade<T>, RevisionController<T>, NavegationConstants, Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	protected static final Logger logger = LoggerFactory.getLogger(GenericEditRevisionFacadeBean.class);
	private static final String SORT_PROPERTY_PARAMETER = "sortProperty";
	
	/**
	 * Facaded Entity 
	 */
	private T entity;

	/**
	 * Data Model
	 */
	private EntityDataModel dataModel;

	/**
	 * <code>true</code> if editing an existing entity. <code>false</code> otherwise.
	 */
	private boolean editing = false;
	    
    /**
     * Cancel View
     */
    protected String cancelView;
    
    /**
     * Return View
     */
    protected String persistReturnView;
    
	/**
	 * Revision Entity
	 */
	private Revision<T> revision;
	
	/**
	 * Search Input
	 */
	protected SearchInput input;
	
	/**
	 * Selected variable
	 */
	private boolean selected = false;
	
	/**
	 * Sort Orders
	 */
	private Map<String, SortOrder> sortsOrders;
	private List<String> sortPriorities;
	private boolean multipleSorting = false;
	
    
	@PostConstruct
	public void createBean() {
		editing = false;
		sortsOrders = new HashMap<String, SortOrder>();
		sortPriorities = new ArrayList<String>();
		setEntity(newEntity());
	}
  	
	@Override
	public T getEntity() {
		return entity;
	}
	
	public void setEntity(T entity) {
		this.entity = entity;
	}
	
	@Override
	public List<T> getAllEntities(){
		GenericWithRevisionDao<T, PK> dao = getDao();
		if(dao == null)
			throw new java.lang.IllegalStateException("DAO not Injected");
        try {
			return dao.findAll();
		} catch (DaoException e) {
			addErrorMessage(e.getLocalizedMessage());
		}
        return null;
	}
	
	@Override
	public String remove() {
		beforeRemove();
		
		GenericWithRevisionDao<T, PK> dao = getDao();
		if(dao == null)
			throw new java.lang.IllegalStateException("DAO not Injected");
		if(logger.isTraceEnabled())
			logger.trace("Removing " + getClass().getSimpleName() + " with Id: " + getEntityId());
    	try {
			dao.remove(entity);
			afterRemove();
			
			return removeOk();
		} catch (DaoException e) {
			addErrorMessage(e.getLocalizedMessage());
	    	return removeError();
		}
	}
	
	public boolean isRemoveOk(){
		return true;
	}	

	@Override
	public String persist() {
		beforePersist();
		
    	GenericWithRevisionDao<T, PK> dao = getDao();
		if(dao == null)
			throw new java.lang.IllegalStateException("DAO not Injected");
		if(logger.isTraceEnabled())
			logger.trace("Persisting: " + entity);

		try{
			if(editing){
				beforeUpdate();
				dao.merge(entity);
				afterUpdate();
			}else{
				beforeCreate();
				dao.persist(entity);
				afterCreate();
			}
		}catch (Exception e) {
			addErrorMessage(e.getLocalizedMessage());
			return currentView();
		}	
		
		afterPersist();
		
		return persistOk();
    }
	
	@Override
    public void create(){
    	if(logger.isTraceEnabled())
    		logger.trace("Create");
		this.entity = newEntity();
	}

	@Override
	public void refresh(){
    	if(logger.isTraceEnabled())
    		logger.trace("Refreshing Entity: " + entity);
		try {
			setEntity(getDao().findById(getEntityId()));
		} catch (DaoException e) {
			addErrorMessage(e.getLocalizedMessage());
		}
	}
    
	
	/* Searcheable Bean */
	
	public SearchInput getInput() {
		if(input == null)
			this.input = createSearchInput();
		return input;
	}
	
	public void search() {
		if(input != null)
			input.setSearchActive(true);
		if(this.dataModel != null)
			this.dataModel.reset();
	}
	
	public boolean isSearchActive() {
		if(input == null)
			return false;
		return input.isSearchActive();
	}
	
	public ExtendedDataModel<T> getDataModel() {
		if(this.dataModel == null)
			this.dataModel = new EntityDataModel();
		return dataModel;
	}

	public List<T> getSearchResults() {
		if(getDao() == null)
			addErrorMessage("DAO Not Injected");
		try {
			if(input.isSearchActive())
				return getDao().findAll();
		} catch (DaoException e) {
			addErrorMessage(e.getMessage());
		}
		return null;
	}
	
	public abstract GenericConverter<T, PK> getConverter();
	
	public String cancel() {
		return persistCancel();
	}

	protected String facadePath(String action){
		return JSFMenuUtil.buildMenuPath(userRolePath(), entityPrefix(), action);
	}
	
	public String showCreate() {
		createBean();
		updateCancelView();		
		return facadePath(createFileName());
	}

	public String showEdit() {
		if(entity == null)
			return facadePath(ERROR_ACTION);
		editing = true;
		updateCancelView();
		return facadePath(editFileName());
	}
	
	public String showRemove() {
		if(entity == null)
			return facadePath(ERROR_ACTION);
		editing = true;
		updateCancelView();
		return facadePath(removeFileName());
	}

	public boolean isEditing(){
		return editing;
	}
	
	protected void setEditing(boolean editing){
		this.editing = editing;
	}

	public String showSearch() {
		this.input = null;
		beforeSearch();
		return facadePath(searchFileName());
	}

	protected abstract GenericWithRevisionDao<T, PK> getDao();
	protected abstract T newEntity();
	
	protected PK getEntityId(){
		return getEntityId(getEntity());
	}
	
	protected abstract PK getEntityId(T t);
	protected abstract String entityPrefix();
	protected abstract String userRolePath();
	protected abstract SearchInput createSearchInput();
	
	// Hook methods for Persistence

	protected void beforePersist(){	
	}
	protected void afterPersist(){
	}

	// Hook methods for Create

	protected void beforeCreate(){	
	}
	protected void afterCreate(){
	}

	// Hook methods for Update

	protected void beforeUpdate(){	
	}
	protected void afterUpdate(){
	}

	// Hook methods for Search
	protected void beforeSearch() {
	}

	// Hook methods for deletion
	
	protected void beforeRemove(){	
	}
	protected void afterRemove(){
	}
	
	protected String persistOk() {
		if(persistReturnView != null && !persistReturnView.isEmpty())
			return persistReturnView + JSFMenuUtil.JSF_REDIRECT;
		return showSearch();
	}
	 
	protected String persistCancel() {
		if(cancelView != null && !cancelView.isEmpty()) 
			return cancelView + JSFMenuUtil.JSF_REDIRECT;
		return showSearch();		
	}
	
	protected String persistError() {
		return facadePath(ERROR_ACTION);
	}

	protected String removeOk() {
		if(persistReturnView != null && !persistReturnView.isEmpty())
			return persistReturnView;
		return showSearch();
	}
	
	protected String removeError() {
		if(persistReturnView != null && !persistReturnView.isEmpty())
			return persistReturnView;
		return showSearch();		
	}
	
	protected String searchFileName() {
		return INDEX_ACTION;
	}

	protected String createFileName() {
		return CREATE_ACTION;
	}
	protected String editFileName() {
		return EDIT_ACTION;
	}
	protected String removeFileName() {
		return REMOVE_ACTION;
	}
	protected String revisionFileName(){
		return REVISION_DETAILS;
	}
	protected String revisionListFileName(){
		return REVISION_LIST;
	}
	
	protected void updateCancelView(){
		FacesContext ctx = FacesContext.getCurrentInstance();
		cancelView = ctx.getViewRoot().getViewId();
	}

	public String getCancelView() {
		return cancelView;
	}

	public void setCancelView(String cancelView) {
		this.cancelView = cancelView;
	}

	public String getPersistReturnView() {
		return persistReturnView;
	}

	public void setPersistReturnView(String persistReturnView) {
		this.persistReturnView = persistReturnView;
	}

	@Override
	public String showMenu() {
		return showSearch();
	}

	@Override
	public void setRevision(Revision<T> revision) {
		this.revision = revision;
	}

	@Override
	public Revision<T> getRevision() {
		return revision;
	}

	@Override
	public String showRevision() {
		if(revision == null)
			return facadePath(ERROR_ACTION);
		editing = false;
		
		updateCancelView();
		return facadePath(revisionFileName());
	}

	@Override
	public String showRevisionList() {
		updateCancelView();
		return facadePath(revisionListFileName());
	}

	@Override
	public List<Revision<T>> getAllRevisions() {
		return getDao().allRevisions(getEntityId());
	}
	
	protected String currentView() {
		return FacesContext.getCurrentInstance().getViewRoot().getViewId();
	}

	public static void addErrorMessage(String localizedMessage) {
		addMessage(localizedMessage, FacesMessage.SEVERITY_ERROR);
	}	

	public static void addInfoMessage(String localizedMessage) {
		addMessage(localizedMessage, FacesMessage.SEVERITY_INFO);
	}
	
	public static void addMessage(String localizedMessage, Severity severity) {
		addMessage(null, localizedMessage, severity);
	}
	
	public static void addMessage(String clientId, String localizedMessage, Severity severity) {
		FacesContext context = FacesContext.getCurrentInstance();
		UIComponent componet = null;
		if(clientId != null)
			componet = context.getViewRoot().findComponent(clientId);
		
		FacesMessage message = new FacesMessage();
		message.setSeverity(severity);
		message.setSummary(localizedMessage);
		message.setDetail(localizedMessage);
		if(componet == null)
			context.addMessage(null, message);
		else
			context.addMessage(componet.getClientId(), message);
	}

	
	private class EntityDataModel extends ExtendedDataModel<T>  {
		private Long rowKey = null;
		private int rowCount = -1;
		private List<T> cache = null;
		
		public EntityDataModel() {
			super();
			reset();
		}
		
		@Override
		public Object getRowKey() {
			return rowKey;
		}

		@Override
		public void setRowKey(Object rowKey) {
			this.rowKey = (Long)rowKey;
		}

		@Override
		public void walk(FacesContext fc, DataVisitor dv, Range range, Object o) {
	        SequenceRange sr = (SequenceRange) range;
	        int currentRow = sr.getFirstRow();
	        int rows = sr.getRows();
	        
			try {
				if(input != null){
					this.rowCount = -1;
					this.cache = getDao().findEntriesBySearchInput(input, currentRow, rows);
			        for (T t : cache) {
			        	dv.process(fc, getEntityId(t), o);
			        }
				}
			} catch (DaoException e) {
			}
		}

		@Override
		public int getRowCount() {
			if(rowCount == -1){
				try {
					//rowCount = (int)getDao().count();
					
					if(input != null){
						rowCount = (int)getDao().countBySearchInput(input);
					}
				} catch (DaoException e) {
					addErrorMessage(e.getMessage());
				}
			}
			return rowCount;
		}

		@Override
		public T getRowData() {
			for(T t : cache){
				if(getEntityId(t).equals(rowKey))
					return t;
			}
			return null;
		}

		@Override
		public int getRowIndex() {
			return -1;
		}

		@Override
		public Object getWrappedData() {
			throw new UnsupportedOperationException();
		}

		@Override
		public boolean isRowAvailable() {
			return rowKey != null;
		}

		@Override
		public void setRowIndex(int arg0) {
			throw new UnsupportedOperationException();
		}

		@Override
		public void setWrappedData(Object arg0) {
			throw new UnsupportedOperationException();
		}

		public void reset() {
			this.rowKey = null;
			this.rowCount = -1;
			this.cache = null;
		}
	}


	@Override
	public boolean isSelected() {
		return selected;
	}

	@Override
	public void setSelected(boolean selected) {
		this.selected = selected;
	}
	
	public void sort() {
        String property = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get(SORT_PROPERTY_PARAMETER);
        if (property != null) {
            SortOrder currentPropertySortOrder = sortsOrders.get(property);
            if (multipleSorting) {
                if (!sortPriorities.contains(property)) {
                    sortPriorities.add(property);
                }
            } else {
                sortsOrders.clear();
            }
            if (currentPropertySortOrder == null || currentPropertySortOrder.equals(SortOrder.descending)) {
                sortsOrders.put(property, SortOrder.ascending);
            } else {
                sortsOrders.put(property, SortOrder.descending);
            }
        }
    }
 
    public void modeChanged(ValueChangeEvent event) {
        reset();
    }
 
    public void reset() {
        sortPriorities.clear();
        sortsOrders.clear();
    }
 
    public boolean isMultipleSorting() {
        return multipleSorting;
    }
 
    public void setMultipleSorting(boolean multipleSorting) {
        this.multipleSorting = multipleSorting;
    }
 
    public List<String> getSortPriorities() {
        return sortPriorities;
    }
 
    public Map<String, SortOrder> getSortsOrders() {
        return sortsOrders;
    }	
}